package cn.mw.cmdb.processor;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import cn.mw.cmdb.entity.InputDataAuth;
import cn.mw.cmdb.entity.InstanceInfo;
import cn.mw.cmdb.entity.PropertyInfo;
import cn.mw.cmdb.param.InstanceChangeRecordDTO;
import cn.mw.cmdb.param.InstanceIdsParam;
import cn.mw.cmdb.param.InstanceSearchParam;
import cn.mw.cmdb.service.ServiceAction;
import cn.mw.cmdb.service.impl.CmdbServiceManage;
import cn.mw.cmdb.service.impl.InstanceServiceImpl;
import cn.mw.cmdb.service.impl.ModelPropertyServiceImpl;
import cn.mw.microMonitorCommon.entity.Constant;
import cn.mw.microMonitorCommon.operatorlog.client.MwInstanceHistoryClient;
import cn.mw.microMonitorCommon.operatorlog.model.MwInstanceHistory;
import cn.mw.microMonitorCommon.security.utils.SecurityUtils;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;

import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

@Slf4j
@Component
public class BusinessProcessEventLogger implements ApplicationListener<BusinessProcessEvent> {

    @Autowired
    private MwInstanceHistoryClient instanceHistoryClient;

    @Autowired
    private CmdbServiceManage cmdbServiceManage;

    private static final String ID = "id";
    private static final String MODEL_ID = "modelId";
    private static final String PROCESS_ID = "processId";
    public static final String SERVICE_ID = "serviceId";

    @Override
    public void onApplicationEvent(BusinessProcessEvent event) {
        log.info("BusinessProcessEventLogger record....");
        // 需要记录的值
        Object ref = event.getRet();
        Object destObj = event.getDestObj();
        ServiceAction action = event.getAction();

        if (ObjUtil.isNull(ref)) {
            log.info("BusinessProcessEventLogger result [null], return....");
            return;
        }
        if (!(ref instanceof Boolean)) {
            // 不是boolean就是流程处理
            if (null == destObj) {
                destObj = new Object();
            }
            log.info("copy source properties: \n{}", JSONUtil.toJsonStr(ref));
            log.info("copy target properties: \n{}", JSONUtil.toJsonStr(destObj));
        }
        InstanceServiceImpl instanceService = (InstanceServiceImpl) cmdbServiceManage.getModelService(InstanceServiceImpl.ID);
        if (ref instanceof InstanceChangeRecordDTO refDTO) {
            log.info("record ref log ...");
            Object processId = refDTO.getProcessId();
            if (processId != null) {
                String processIdStr = String.valueOf(processId);
                log.info("processIdStr：{}", processIdStr);
                List<InstanceInfo> listObj = refDTO.getInstanceInfos();
                List<String> instanceIds = listObj.stream().map(InstanceInfo::getId).toList();
                InstanceSearchParam instanceSearchParam = new InstanceSearchParam();
                instanceSearchParam.setInstanceIds(instanceIds);
                try {
                    List<InstanceInfo> instanceInfos = instanceService.selectListByIds(instanceSearchParam);
                    if (CollUtil.isNotEmpty(instanceInfos)) {
                        ModelPropertyServiceImpl modelPropertyService = (ModelPropertyServiceImpl) cmdbServiceManage.getModelService(ModelPropertyServiceImpl.ID);
                        List<MwInstanceHistory> historyList = new ArrayList<>();
                        for (InstanceInfo instanceInfo : instanceInfos) {
                            Map<String, PropertyInfo> instanceInputFieldViewMap = getInstanceInputFieldViewMap(modelPropertyService, instanceInfo.getModelId(), instanceInfo.getFinanceParentId());
                            MwInstanceHistory instanceHistory = convertInstanceHistory(instanceInfo, instanceInputFieldViewMap, action);
                            instanceHistory.setProcessId(processIdStr);
                            historyList.add(instanceHistory);
                        }
                        instanceHistoryClient.addInstanceHistory(historyList);
                    }
                    return;
                } catch (Exception e) {
                    log.error("executor change history: ", e);
                }
            }
        }

        List<MwInstanceHistory> addList = convertByAction(ref, destObj, action);
        instanceHistoryClient.addInstanceHistory(addList);

    }

    @SneakyThrows
    private List<MwInstanceHistory> convertByAction(Object ref, Object destObj, ServiceAction action) {
        InstanceServiceImpl instanceService = (InstanceServiceImpl) cmdbServiceManage.getModelService(InstanceServiceImpl.ID);
        ModelPropertyServiceImpl modelPropertyService = (ModelPropertyServiceImpl) cmdbServiceManage.getModelService(ModelPropertyServiceImpl.ID);

        List<MwInstanceHistory> list = new ArrayList<>();
        switch (action) {
            case add -> {
                if (null == destObj) {
                    destObj = new Object();
                }
                BeanUtils.copyProperties(ref, destObj);
            }
            case update -> {
                if (destObj instanceof InstanceInfo instanceInfo) {
                    Map<String, PropertyInfo> instanceInputFieldViewMap = getInstanceInputFieldViewMap(modelPropertyService, instanceInfo.getModelId(), instanceInfo.getFinanceParentId());
                    MwInstanceHistory instanceHistory = convertInstanceHistory(instanceInfo, instanceInputFieldViewMap, action);
                    list.add(instanceHistory);
                }

            }
            case remove -> {
                if (destObj instanceof InstanceIdsParam delParam) {
                    List<String> ids = delParam.getIds();
                    list = ids.stream().map(id -> {
                        MwInstanceHistory instanceHistory = new MwInstanceHistory();
                        instanceHistory.setInstanceId(id);
                        instanceHistory.setOperatorType(action.name());
                        return instanceHistory;
                    }).toList();
                }
            }
            case batchAdd -> {
                if (ref instanceof InstanceChangeRecordDTO refDTO) {
                    List<InstanceInfo> listObj = refDTO.getInstanceInfos();
                    if (listObj.size() == 1) {
                        InstanceInfo instanceInfo = listObj.get(0);

                        Map<String, PropertyInfo> instanceInputFieldViewMap = getInstanceInputFieldViewMap(modelPropertyService, instanceInfo.getModelId(), instanceInfo.getFinanceParentId());

                        MwInstanceHistory instanceHistory = convertInstanceHistory(instanceInfo, instanceInputFieldViewMap, ServiceAction.add);
                        list.add(instanceHistory);
                    } else {
                        List<String> instanceIds = listObj.stream().map(InstanceInfo::getId).toList();
                        InstanceSearchParam instanceSearchParam = new InstanceSearchParam();
                        instanceSearchParam.setInstanceIds(instanceIds);
                        List<InstanceInfo> instanceInfos = instanceService.selectListByIds(instanceSearchParam);
                        for (InstanceInfo obj : instanceInfos) {
                            Map<String, PropertyInfo> instanceInputFieldViewMap = getInstanceInputFieldViewMap(modelPropertyService, obj.getModelId(), obj.getFinanceParentId());
                            MwInstanceHistory instanceHistory = convertInstanceHistory(obj, instanceInputFieldViewMap, action);
                            list.add(instanceHistory);
                        }
                    }
                }
            }
            default -> {
                log.info("unknown request type: {}", action);
            }
        }
        return list;
    }

    private Map<String, PropertyInfo> getInstanceInputFieldViewMap(ModelPropertyServiceImpl modelPropertyService, String modelId, String financeParentId) throws Exception {
        Map<String, List<PropertyInfo>> propertyFieldMap = modelPropertyService.getModelAllPropertyList(Arrays.asList(modelId) ,financeParentId);
        List<PropertyInfo> propertyFieldList = propertyFieldMap.get(modelId);
        if (CollUtil.isEmpty(propertyFieldList)) {
            log.info("getPropertyFieldByModelId return: null, param is modelId: {}, inputType: {}", modelId, financeParentId);
            return Map.of();
        }
        return propertyFieldList.stream().collect(Collectors.toMap(PropertyInfo::getId, Function.identity(), (k1, k2) -> k1));
    }

    public MwInstanceHistory convertInstanceHistory(Object ref, Map<String, PropertyInfo> instanceInputFieldViewMap, ServiceAction action) {
        MwInstanceHistory instanceHistory = new MwInstanceHistory();
        Map<String, Object> refMap ;
        if (ref instanceof Map<?, ?>) {
            refMap = (Map<String, Object>) ref;
        } else {
            refMap = beanToMap(ref);
        }
        refMap = dealData(refMap);
        Map<String, Object> fieldInfoViewMap = new HashMap<>();
        for (Map.Entry<String, Object> entry : refMap.entrySet()) {
            if (instanceInputFieldViewMap.containsKey(entry.getKey()) && entry.getValue() != null && !entry.getValue().equals("")) {
                fieldInfoViewMap.put(entry.getKey(), entry.getValue());
            }
        }

        Object serviceId = refMap.get(SERVICE_ID);
        if (ObjUtil.isEmpty(serviceId)) {
            log.error("service_id is empty, param: {}", JSONUtil.toJsonStr(refMap));
            return instanceHistory;
        }
        String serviceIdStr = serviceId.toString();
        if (StrUtil.isNotBlank(serviceIdStr) && !InstanceServiceImpl.ID.equals(serviceIdStr)) {
            log.info("If the type is not instance, skip it. serviceId: {}", serviceId);
            return instanceHistory;
        }
        if (refMap.containsKey(Constant.MSG)) {
            return instanceHistory;
        }
        if (refMap.containsKey(ID)) {
            String instanceId = (String) refMap.get(ID);
            instanceHistory.setInstanceId(instanceId);
        }
        if (refMap.containsKey(MODEL_ID)) {
            String modelId = (String) refMap.get(MODEL_ID);
            instanceHistory.setModelId(modelId);
        }
        instanceHistory.setOperatorType(action.name());
        instanceHistory.setOperatorUser(String.valueOf(SecurityUtils.getUserId()));
        String content = JSONUtil.toJsonStr(fieldInfoViewMap);
        instanceHistory.setContent(content);
        if (fieldInfoViewMap.containsKey(InputDataAuth.UserKey)) {
            List<Integer> principal = (List<Integer>) fieldInfoViewMap.get(InputDataAuth.UserKey);
            instanceHistory.setPrincipal(principal);
        }
        if (refMap.containsKey(PROCESS_ID)) {
            String processId = String.valueOf(refMap.get(PROCESS_ID));
            instanceHistory.setProcessId(processId);
        }
        return instanceHistory;
    }

    private static Map<String, Object> dealData(Map<String, Object> inputMap) {
        Map<String, Object> result = new HashMap<>();
        inputMap.forEach((k, v) -> {
            if (k.equals("data") && v != null && !v.equals("")) {
                HashMap<String,Object> hashMap = (HashMap<String, Object>) v;
                hashMap.forEach((k1, v1) -> {
                    result.put(k1, v1);
                });
            }else {
                result.put(k, v);
            }
        });
        return result;
    }

    public Map<String, Object> beanToMap(Object object) {
        Map<String, Object> map = new HashMap<>();
        try {
            BeanInfo beanInfo = Introspector.getBeanInfo(object.getClass());
            PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
            for (PropertyDescriptor property : propertyDescriptors) {
                String key = property.getName();
                if (key.compareToIgnoreCase("class") == 0) {
                    continue;
                }
                Method getter = property.getReadMethod();
                Object value = getter != null ? getter.invoke(object) : null;
                map.put(key, value);
            }
            //key 可能会把自己的class 和hashcode编进去，直接去掉
            map.remove("class");

        } catch (Exception e) {
            e.printStackTrace();
            return new HashMap<>();
        }

        return map;
    }

    public void addBatchUpdInstanceInfo(List<InstanceInfo> instanceInfos) {
        try {
            log.info("record instance [batchUpdate] log, request: {}", JSONUtil.toJsonStr(instanceInfos));
            InstanceServiceImpl instanceService = (InstanceServiceImpl) cmdbServiceManage.getModelService(InstanceServiceImpl.ID);
            List<String> instanceIds = instanceInfos.stream().map(InstanceInfo::getId).toList();
            InstanceSearchParam instanceSearchParam = new InstanceSearchParam();
            instanceSearchParam.setInstanceIds(instanceIds);
            instanceInfos = instanceService.selectListByIds(instanceSearchParam);

            ModelPropertyServiceImpl modelPropertyService = (ModelPropertyServiceImpl) cmdbServiceManage.getModelService(ModelPropertyServiceImpl.ID);
            List<MwInstanceHistory> list = new ArrayList<>();
            for (InstanceInfo obj : instanceInfos) {
                Map<String, PropertyInfo> instanceInputFieldViewMap = getInstanceInputFieldViewMap(modelPropertyService, obj.getModelId(), obj.getFinanceParentId());
                MwInstanceHistory instanceHistory = convertInstanceHistory(obj, instanceInputFieldViewMap, ServiceAction.batchUpd);
                list.add(instanceHistory);
            }
            instanceHistoryClient.addInstanceHistory(list);
        } catch (Exception e) {
            log.error("addBatchUpdInstanceInfo failed: ", e);
        }

    }

    public void addBatchRemoveInstanceInfo(List<InstanceInfo> instanceInfos, String processId) {
        try {
            log.info("record instance [batchRemove] log, request: {}", JSONUtil.toJsonStr(instanceInfos));
            List<MwInstanceHistory> list = new ArrayList<>();
            boolean singleRemove = instanceInfos.size() == 1;
            for (InstanceInfo obj : instanceInfos) {
                MwInstanceHistory instanceHistory = new MwInstanceHistory();
                instanceHistory.setInstanceId(obj.getId());
                instanceHistory.setModelId(obj.getModelId());
                String operatorType = singleRemove ? ServiceAction.remove.name() : ServiceAction.batchRemove.name();
                instanceHistory.setOperatorType(operatorType);
                instanceHistory.setContent(JSONUtil.toJsonStr(obj));
                instanceHistory.setOperatorUser(String.valueOf(SecurityUtils.getUserId()));
                if (CollUtil.isNotEmpty(obj.getUsers())) {
                    List<Integer> principal = obj.getUsers().stream().map(Long::intValue).toList();
                    instanceHistory.setPrincipal(principal);
                }
                if (StrUtil.isNotBlank(processId)) {
                    instanceHistory.setProcessId(processId);
                }
                list.add(instanceHistory);
            }

            instanceHistoryClient.addInstanceHistory(list);
        } catch (Exception e) {
            log.error("addBatchRemoveInstanceInfo failed: ", e);
        }
    }
}
